/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package com.futuremind.recyclerviewfastscroll;

import com.futuremind.recyclerviewfastscroll.viewprovider.DefaultScrolledViewProvider;
import com.futuremind.recyclerviewfastscroll.viewprovider.ScrollerViewProvider;

import ohos.agp.colors.RgbColor;
import ohos.agp.components.*;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.utils.Color;
import ohos.app.Context;
import ohos.multimodalinput.event.MmiPoint;
import ohos.multimodalinput.event.TouchEvent;

import java.util.ArrayList;
import java.util.List;

/**
 * Utils
 *
 * @since 2021-03-23
 */
public class FastScroller extends DirectionalLayout {
    private static final int STYLE_NONE = -1;
    private static final int SCROLL_STATE_IDLE = 0; // 停止滚动
    private static final int SCROLL_STATE_DRAGGING = 1; // 正在被外部拖拽,一般为用户正在用手指滚动

    private int mScrollState = SCROLL_STATE_IDLE;
    private int oldScrollState = SCROLL_STATE_IDLE;
    /**
     * scrollListener 变量
     *
     * @noinspection checkstyle:SingleSpaceSeparator
     */
    private ListContainerScrolledListener scrollListener = new ListContainerScrolledListener(this);
    private ListContainer listContainer;
    private Component bubble;
    private Component handle;
    private Text bubbleTextView;
    private int bubbleOffset;
    private Color handleColor;
    private Color bubbleColor;
    private Color bubbleTextColor;
    private int bubbleTextSize;

    private int scrolledOrientation;
    private int totalScrollH = 0;
    private int totalScrollW = 0;
    private int maxVisibility;
    private boolean isManuallyChangingPosition;
    private boolean isShowHandle;
    private ScrollerViewProvider viewProvider;
    private SectionTitleProvider titleProvider;
    private boolean isRun = true;
    private int mMsta = -1;
    private boolean isBack = false;
    private List<OnScrollListener> mScrollListeners;
    private int mTouchSlop = 8;
    private float mFt = 0.5f;
    private int mSum1 = 1;
    private int mSum2 = 2;

    public FastScroller(Context context) {
        super(context);
    }

    public FastScroller(Context context, AttrSet attrSet) {
        super(context, attrSet);
        setClipEnabled(false);
        initArrange();
        initAttrSet(attrSet);
    }

    public FastScroller(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        setClipEnabled(false);
        initArrange();
        initAttrSet(attrSet);
    }

    private void initAttrSet(AttrSet attrSet) {
        handleColor = AttrValue.get(attrSet, "handleColor", new Color(STYLE_NONE));
        bubbleColor = AttrValue.get(attrSet, "bubbleColor", new Color(STYLE_NONE));
        bubbleTextColor = AttrValue.get(attrSet, "bubbleTextColor", new Color(STYLE_NONE));
        bubbleTextSize = AttrValue.getDimension(attrSet, "bubbleTextSize", STYLE_NONE);
        maxVisibility = getVisibility();
        setOrientation(getOrientation());
        setViewProvider(new DefaultScrolledViewProvider());
    }

    /**
     * Enables custom layout for {@link FastScroller}.
     *
     * @param viewProvider A {@link ScrollerViewProvider} for the {@link FastScroller} to use when building layout.
     */
    public void setViewProvider(ScrollerViewProvider viewProvider) {
        removeAllComponents();
        this.viewProvider = viewProvider;
        viewProvider.setFastScroller(this);
        bubble = viewProvider.provideBubbleView(this);
        handle = viewProvider.provideHandleView(this);
        bubbleTextView = viewProvider.provideBubbleTextView();

        addComponent(bubble);
        addComponent(handle);
    }

    public void setListContainer(ListContainer listContainer) {
        this.listContainer = listContainer;
        if (listContainer.getItemProvider() instanceof SectionTitleProvider) {
            titleProvider = (SectionTitleProvider) listContainer.getItemProvider();
        }
        listContainer.setScrolledListener(scrollListener);

        isShowHandle = listContainer.getItemPosByVisibleIndex(0) == mMsta;
        invalidateVisibility();

        listContainer.setLayoutRefreshedListener(new LayoutRefreshedListener() {
            @Override
            public void onRefreshed(Component component) {
                Component cp = listContainer.getComponentAt(0);
                if (cp != null && totalScrollH == 0) {
                    int hg = cp.getHeight();
                    int wd = cp.getWidth();
                    totalScrollH = hg * listContainer.getItemProvider().getCount();
                    totalScrollW = wd * listContainer.getItemProvider().getCount();
                }
            }
        });

        listContainer.addItemVisibilityChangedListener(new ListContainer.ItemVisibilityChangedListener() {
            @Override
            public void onItemAdded(Component component, int i2) {
                invalidateVisibility();
            }

            @Override
            public void onItemRemoved(Component component, int i2) {
                invalidateVisibility();
            }
        });
        listContainer.setTouchEventListener(new TouchEventListener() {
            @Override
            public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
                onListTouchEvent(touchEvent);
                return false;
            }
        });
        addOnScrollListener(new OnScrollListener() {
            @Override
            public void onFastScrollStateChanged(Component component, int newScrollState) {
                super.onFastScrollStateChanged(component, newScrollState);
                if (newScrollState == SCROLL_STATE_IDLE && oldScrollState != SCROLL_STATE_IDLE) {
                    getViewProvider().onScrollFinished();
                } else{
                    getViewProvider().onScrollStarted();
                }
                oldScrollState = newScrollState;
            }
        });
    }

    private void initArrange() {
        setArrangeListener(new ArrangeListener() {
            @Override
            public boolean onArrange(int i0, int i1, int i2, int i3) {
                isShowHandle = listContainer.getItemPosByVisibleIndex(0) == mMsta;
                invalidateVisibility();
                initHandleMovement();
                bubbleOffset = viewProvider.getBubbleOffset();
                applyStyling();
                return false;
            }
        });
    }

    public int getTotalScrollH() {
        return totalScrollH;
    }

    public int getTotalScrollW() {
        return totalScrollW;
    }

    private void initHandleMovement() {
        handle.setTouchEventListener(new TouchEventListener() {
            @Override
            public boolean onTouchEvent(Component component, TouchEvent event) {
                int et = event.getAction();
                MmiPoint mt = event.getPointerPosition(0); // 屏幕的高度

                mLoading(et, event, mt);
                return isBack;
            }
        });
    }

    private void mLoading(int et, TouchEvent event, MmiPoint mt) {
        switch (et) {
            case TouchEvent.PRIMARY_POINT_DOWN:
                if (titleProvider != null && event.getAction() == TouchEvent.PRIMARY_POINT_DOWN) {
                    viewProvider.onHandleGrabbed();
                }
                break;
            case TouchEvent.POINT_MOVE:
                final int gx = (int) (mt.getX() + mFt);
                final int gy = (int) (mt.getY() + mFt);
                final int dx = -gx;
                final int dy = -gy;
                isManuallyChangingPosition = true;
                isBack = true;
                isRun = false;
                if (Math.abs(dy) >= handle.getHeight() / mSum2 || Math.abs(dx) >= handle.getHeight() / mSum2) {
                    isRun = true;
                }
                float relativePos = getRelativeTouchPosition(event);
                setScrolledPosition(relativePos);
                setListContainerPosition(relativePos);

                break;
            case TouchEvent.PRIMARY_POINT_UP:
                isBack = true;
                isManuallyChangingPosition = false;
                if (titleProvider != null) {
                    viewProvider.onHandleReleased();
                }

                break;
            default:
                break;
        }
    }

    /**
     * to be notified of user scrolling
     *
     * @param relativePos 1
     */
    public void setListContainerPosition(float relativePos) {
        if (listContainer == null) {
            return;
        }
        int itemCount = listContainer.getItemProvider().getCount();
        int targetPos = (int) Utils.getValueInRange(0, itemCount - mSum1, (int) (relativePos * (float) itemCount));
        if (isRun) {
            listContainer.scrollTo(targetPos);
        }
        if (titleProvider != null && bubbleTextView != null) {
            bubbleTextView.setText(titleProvider.getSectionTitle(targetPos));
        }
    }

    void setScrolledPosition(float relativePos) {
        if (!isRun) {
            return;
        }
        if (isVertical()) {
            bubble.setTranslationY(Utils.getValueInRange(
                    0,
                    getHeight() - bubble.getHeight(),
                    relativePos * (getHeight() - handle.getHeight()) + bubbleOffset) - bubble.getTop()
            );

            handle.setTranslationY(Utils.getValueInRange(
                    0,
                    getHeight() - handle.getHeight(),
                    relativePos * (getHeight() - handle.getHeight())) - handle.getTop()
            );
        } else {
            bubble.setTranslationX(Utils.getValueInRange(
                    0,
                    getWidth() - bubble.getWidth(),
                    relativePos * (getWidth() - handle.getWidth()) + bubbleOffset) - getLeft()
            );

            handle.setTranslationX(Utils.getValueInRange(
                    0,
                    getWidth() - handle.getWidth(),
                    relativePos * (getWidth() - handle.getWidth())) - getLeft()
            );
        }
        scrollListener.notifyListeners(relativePos);
    }

    private void invalidateVisibility() {
        if (listContainer.getItemProvider() == null || listContainer.getItemProvider().getCount() == 0) {
            if (isShowHandle || isListCnotScrollable() || maxVisibility != Component.VISIBLE) {
                super.setVisibility(INVISIBLE);
            }
        } else {
            super.setVisibility(VISIBLE);
        }
    }

    private boolean isListCnotScrollable() {
        if (isVertical()) {
            return listContainer.getComponentAt(0) != null
                    && listContainer.getComponentAt(0).getHeight()
                    * listContainer.getItemProvider().getCount() <= listContainer.getHeight();
        } else {
            return listContainer.getItemPosByVisibleIndex(0) != -mSum1
                    && listContainer.getComponentAt(listContainer.getItemPosByVisibleIndex(0)).getWidth()
                    * listContainer.getItemProvider().getCount() <= listContainer.getWidth();
        }
    }

    private void applyStyling() {
        if (bubbleColor.getValue() != STYLE_NONE) {
            setBackgroundTint(bubbleTextView, bubbleColor);
        }
        if (handleColor.getValue() != STYLE_NONE) {
            setBackgroundTint(handle, handleColor);
        }
        if (bubbleTextColor.getValue() != STYLE_NONE) {
            bubbleTextView.setTextColor(bubbleTextColor);
        }
        if (bubbleTextSize != STYLE_NONE) {
            bubbleTextView.setTextSize(bubbleTextSize);
        }
    }

    private void setBackgroundTint(Component view, Color color) {
        final ShapeElement background = view.getBackgroundElement() instanceof ShapeElement
                ? (ShapeElement) view.getBackgroundElement() : null;
        if (background == null) {
            return;
        }
        background.setRgbColor(RgbColor.fromArgbInt(color.getValue()));
        Utils.setBackground(view, background);
    }

    private float getRelativeTouchPosition(TouchEvent event) {
        if (isVertical()) {
            float yinParent = event.getPointerScreenPosition(0).getY() - Utils.getViewRawY(handle);
            return yinParent / (getHeight() - handle.getHeight());
        } else {
            float xinParent = event.getPointerScreenPosition(0).getX() - Utils.getViewRawX(handle);
            return xinParent / (getWidth() - handle.getWidth());
        }
    }

    @Override
    public void setVisibility(int visibility) {
        maxVisibility = visibility;
        invalidateVisibility();
    }

    @Override
    public void setOrientation(int orientation) {
        scrolledOrientation = orientation;
        super.setOrientation(orientation == HORIZONTAL ? VERTICAL : HORIZONTAL);
    }

    /**
     * Set the background color of the bubble.
     *
     * @param color Color in hex notation with alpha channel, e.g. 0xFFFFFFFF
     */
    public void setBubbleColor(Color color) {
        bubbleColor = color;
        invalidate();
    }

    /**
     * Set the background color of the handle.
     *
     * @param color Color in hex notation with alpha channel, e.g. 0xFFFFFFFF
     */
    public void setHandleColor(Color color) {
        handleColor = color;
        invalidate();
    }

    /**
     * Sets the text appearance of the bubble.
     *
     * @param bubbleTextColor The id of the resource to be used as text Color of the bubble.
     */
    public void setBubbleTextColor(Color bubbleTextColor) {
        this.bubbleTextColor = bubbleTextColor;
        invalidate();
    }

    /**
     * Sets the text appearance of the bubble.
     *
     * @param bubbleTextSize The id of the resource to be used as text TextSize of the bubble.
     */
    public void setBubbleTextSize(int bubbleTextSize) {
        this.bubbleTextSize = bubbleTextSize;
        invalidate();
    }

    /**
     * Sets the text appearance of the bubble.
     *
     * @param bubbleTextWeight The id of the resource to be used as text Weight of the bubble.
     */
    public void setBubbleTextWeight(int bubbleTextWeight) {
        invalidate();
    }

    /**
     * to be notified of user scrolling
     *
     * @param listener 1
     */
    public void addScrolledListener(ListContainerScrolledListener.ScrolledListener listener) {
        scrollListener.addScrolledListener(listener);
    }

    /**
     * notified of user scrolling
     *
     * @return 1
     */
    public boolean isVertical() {
        return scrolledOrientation == VERTICAL;
    }

    boolean shouldUpdateHandlePosition() {
        return handle != null && !isManuallyChangingPosition && listContainer.getChildCount() > 0;
    }

    ScrollerViewProvider getViewProvider() {
        return viewProvider;
    }

    boolean onListTouchEvent(TouchEvent touchEvent) {
        final boolean canScrollHorizontally = getOrientation() == HORIZONTAL;
        final boolean canScrollVertically = getOrientation() == VERTICAL;
        MmiPoint mt = touchEvent.getPointerPosition(0);
        final int action = touchEvent.getAction();
        switch (action) {
            case TouchEvent.PRIMARY_POINT_DOWN:
                break;
            case TouchEvent.POINT_MOVE:
                final int gx = (int) (mt.getX() + 0.5f);
                final int gy = (int) (mt.getY() + 0.5f);
                int dx = -gx;
                int dy = -gy;
                if (mScrollState != SCROLL_STATE_DRAGGING) {
                    boolean isTartcroll = false;
                    if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
                        isTartcroll = true;
                    }
                    if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
                        isTartcroll = true;
                    }
                    if (isTartcroll) {
                        setScrollState(SCROLL_STATE_DRAGGING);
                    }
                }

                break;
            case TouchEvent.PRIMARY_POINT_UP:
                setScrollState(SCROLL_STATE_IDLE);
                break;
            default:
                break;
        }
        return false;
    }

    /**
     * setScrollState
     *
     * @param state1 1
     */
    void setScrollState(int state1) {
        if (mScrollState == state1) {
            return;
        }
        mScrollState = state1;
        if (mScrollListeners != null) {
            for (int i = mScrollListeners.size() - 1; i >= 0; i--) {
                mScrollListeners.get(i).onFastScrollStateChanged(this, state1);
            }
        }
    }

    /**
     * dispatchOnContentScrolled
     *
     * @param component 组件
     * @param newX      新的x坐标
     * @param newY      新的y坐标
     * @param oldX      旧的x坐标
     * @param oldY      旧的y坐标
     */
    void dispatchOnContentScrolled(Component component, int newX, int newY, int oldX, int oldY) {
        if (mScrollListeners != null) {
            for (int i = mScrollListeners.size() - 1; i >= 0; i--) {
                mScrollListeners.get(i).onFastContentScrolled(component, newX, newY, oldX, oldY);
            }
        }
    }

    /**
     * addOnScrollListener
     *
     * @param listener 接口
     */
    public void addOnScrollListener(OnScrollListener listener) {
        if (mScrollListeners == null) {
            mScrollListeners = new ArrayList<>();
        }
        mScrollListeners.add(listener);
    }

    /**
     * OnScrollListener
     *
     * @since 2021-03-23
     */
    public abstract static class OnScrollListener {
        /**
         * onFastScrollStateChanged
         *
         * @param component      组件
         * @param newScrollState 新的滚动状态
         */
        public void onFastScrollStateChanged(Component component, int newScrollState) {
        }

        /**
         * onFastContentScrolled
         *
         * @param component 组件
         * @param newX      新的x轴
         * @param newY      新的y轴
         * @param oldX      旧的x轴
         * @param oldY      旧的y轴
         */
        public void onFastContentScrolled(Component component, int newX, int newY, int oldX, int oldY) {
        }
    }
}
